//
//  ACSafeObject.m
//  ACFramework
//
//  Created by caoyq on 2019/4/14.
//

#import "ACSafeObject.h"
#import <objc/runtime.h>
#import <objc/message.h>

NSString *const ACSafeNotification = @"_ACSafeNotification_";

#define SafeAssert(condition, ...) \
if (!(condition)){ SafeLog(__FILE__, __FUNCTION__, __LINE__, __VA_ARGS__);} \
NSAssert(condition, @"%@", __VA_ARGS__);

void SafeLog(const char* file, const char* func, int line, NSString* fmt, ...)
{
    va_list args; va_start(args, fmt);
    NSLog(@"%s|%s|%d|%@", file, func, line, [[NSString alloc] initWithFormat:fmt arguments:args]);
    va_end(args);
}

/**
 * 安全协议
 */
@interface NSSafeProxy : NSObject

@end

@implementation NSSafeProxy

- (void)dealException:(NSString*)info
{
    NSString* msg = [NSString stringWithFormat:@"NSSafeProxy: %@", info];
    SafeAssert(0, msg);
}

@end

@implementation NSObject(ACSwizzle)

+ (void)swizzleClassMethod:(SEL)originalSelector withMethod:(SEL)newSelector
{
    Class cls = [self class];
    Method originalMethod = class_getClassMethod(cls, originalSelector);
    Method swizzledMethod = class_getClassMethod(cls, newSelector);
    
    Class metacls = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
    if (class_addMethod(metacls,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* swizzing super class method, added if not exist */
        class_replaceMethod(metacls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

- (void)swizzleInstanceMethod:(SEL)originalSelector withMethod:(SEL)newSelector
{
    Class cls = [self class];
    /* if current class not exist selector, then get super*/
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
    /* add selector if not exist, implement append with method */
    if (class_addMethod(cls,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* replace class instance method, added if selector not exist */
        /* for class cluster , it always add new selector here */
        class_replaceMethod(cls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(cls,
                            newSelector,
                            class_replaceMethod(cls,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}

@end

#pragma mark - NSObject 安全机制

@implementation NSObject(ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSObject* obj = [[NSObject alloc] init];
        [obj swizzleInstanceMethod:@selector(addObserver:forKeyPath:options:context:) withMethod:@selector(hookAddObserver:forKeyPath:options:context:)];
        [obj swizzleInstanceMethod:@selector(removeObserver:forKeyPath:) withMethod:@selector(hookRemoveObserver:forKeyPath:)];
        
        //拦截消息的转发，实现自定义
        [obj swizzleInstanceMethod:@selector(methodSignatureForSelector:) withMethod:@selector(hookMethodSignatureForSelector:)];
        [obj swizzleInstanceMethod:@selector(forwardInvocation:) withMethod:@selector(hookForwardInvocation:)];
    });
}

- (void)hookAddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    if (!observer || !keyPath.length) {
        SafeAssert(NO, @"hookAddObserver invalid args: %@", self);
        return;
    }
    @try {
        [self hookAddObserver:observer forKeyPath:keyPath options:options context:context];
    }
    @catch (NSException *exception) {
        NSLog(@"hookAddObserver ex: %@", [exception callStackSymbols]);
    }
}

- (void)hookRemoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
    if (!observer || !keyPath.length) {
        SafeAssert(NO, @"hookRemoveObserver invalid args: %@", self);
        return;
    }
    @try {
        [self hookRemoveObserver:observer forKeyPath:keyPath];
    }
    @catch (NSException *exception) {
        NSLog(@"hookRemoveObserver ex: %@", [exception callStackSymbols]);
    }
}

- (NSMethodSignature*)hookMethodSignatureForSelector:(SEL)aSelector
{
    /* 如果 当前类有methodSignatureForSelector实现，NSObject的实现直接返回nil
     * 子类实现如下：
     *          NSMethodSignature* sig = [super methodSignatureForSelector:aSelector];
     *          if (!sig) {
     *              //当前类的methodSignatureForSelector实现
     *              //如果当前类的methodSignatureForSelector也返回nil
     *          }
     *          return sig;
     */
    NSMethodSignature* sig = [self hookMethodSignatureForSelector:aSelector];
    if (!sig){
        if (class_getMethodImplementation([NSObject class], @selector(methodSignatureForSelector:))
            != class_getMethodImplementation(self.class, @selector(methodSignatureForSelector:)) ){
            return nil;
        }
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return sig;
}

- (void)hookForwardInvocation:(NSInvocation*)invocation
{
    NSString* info = [NSString stringWithFormat:@"unrecognized selector [%@] sent to %@", NSStringFromSelector(invocation.selector), NSStringFromClass(self.class)];
    [[NSNotificationCenter defaultCenter] postNotificationName:ACSafeNotification object:self userInfo:@{@"invocation":invocation}];
    [[NSSafeProxy new] dealException:info];
}

@end

#pragma mark - NSString/NSMutableString 安全机制

@implementation NSString (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法不用在NSMutableString里再swizz一次 */
        [NSString swizzleClassMethod:@selector(stringWithUTF8String:) withMethod:@selector(hookStringWithUTF8String:)];
        [NSString swizzleClassMethod:@selector(stringWithCString:encoding:) withMethod:@selector(hookStringWithCString:encoding:)];
        
        /* init方法 */
        NSString* obj = [NSString alloc];//NSPlaceholderString
        [obj swizzleInstanceMethod:@selector(initWithCString:encoding:) withMethod:@selector(hookInitWithCString:encoding:)];
        
        /* 普通方法 */
        obj = [[NSString alloc] init];
        [obj swizzleInstanceMethod:@selector(stringByAppendingString:) withMethod:@selector(hookStringByAppendingString:)];
        [obj swizzleInstanceMethod:@selector(substringFromIndex:) withMethod:@selector(hookSubstringFromIndex:)];
        [obj swizzleInstanceMethod:@selector(substringToIndex:) withMethod:@selector(hookSubstringToIndex:)];
        [obj swizzleInstanceMethod:@selector(substringWithRange:) withMethod:@selector(hookSubstringWithRange:)];
        [obj swizzleInstanceMethod:@selector(rangeOfString:options:range:locale:) withMethod:@selector(hookRangeOfString:options:range:locale:)];
    });
}

+ (NSString*)hookStringWithUTF8String:(const char *)nullTerminatedCString
{
    if (NULL != nullTerminatedCString) {
        return [self hookStringWithUTF8String:nullTerminatedCString];
    }
    SafeAssert(NO, @"NSString invalid args hookStringWithUTF8String nil cstring");
    return nil;
}

+ (nullable instancetype)hookStringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
{
    if (NULL != cString){
        return [self hookStringWithCString:cString encoding:enc];
    }
    SafeAssert(NO, @"NSString invalid args hookStringWithCString nil cstring");
    return nil;
}

- (nullable instancetype)hookInitWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding
{
    if (NULL != nullTerminatedCString){
        return [self hookInitWithCString:nullTerminatedCString encoding:encoding];
    }
    SafeAssert(NO, @"NSString invalid args hookInitWithCString nil cstring");
    return nil;
}

- (NSString *)hookStringByAppendingString:(NSString *)aString
{
    if (aString){
        return [self hookStringByAppendingString:aString];
    }
    SafeAssert(NO, @"StringByAppendingString: aString is nil");
    return self;
}

- (NSString *)hookSubstringToIndex:(NSUInteger)to
{
    if (to <= self.length) {
        return [self hookSubstringToIndex:to];
    }
    SafeAssert(NO, @"SubstringToIndex: index is over length");
    return self;
}

- (NSString *)hookSubstringFromIndex:(NSUInteger)from
{
    if (from <= self.length) {
        return [self hookSubstringFromIndex:from];
    }
    SafeAssert(NO, @"SubstringFromIndex: index is over length");
    return nil;
}

- (NSString *)hookSubstringWithRange:(NSRange)range
{
    if (range.location + range.length <= self.length) {
        return [self hookSubstringWithRange:range];
    }else if (range.location < self.length){
        return [self hookSubstringWithRange:NSMakeRange(range.location, self.length-range.location)];
    }
    SafeAssert(NO, @"SubstringWithRange: range is over");
    return nil;
}

- (NSRange)hookRangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(nullable NSLocale *)locale
{
    if (searchString){
        if (range.location + range.length <= self.length) {
            return [self hookRangeOfString:searchString options:mask range:range locale:locale];
        }else if (range.location < self.length){
            return [self hookRangeOfString:searchString options:mask range:NSMakeRange(range.location, self.length-range.location) locale:locale];
        }
        return NSMakeRange(NSNotFound, 0);
    }
    SafeAssert(NO, @"hookRangeOfString:options:range:locale: searchString is nil");
    return NSMakeRange(NSNotFound, 0);
}

@end

@implementation NSMutableString (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        /* init方法 */
        NSMutableString* obj = [NSMutableString alloc];//NSPlaceholderMutableString
        [obj swizzleInstanceMethod:@selector(initWithCString:encoding:) withMethod:@selector(hookInitWithCString:encoding:)];
        
        /* 普通方法 */
        obj = [[NSMutableString alloc] init];
        [obj swizzleInstanceMethod:@selector(appendString:) withMethod:@selector(hookAppendString:)];
        [obj swizzleInstanceMethod:@selector(insertString:atIndex:) withMethod:@selector(hookInsertString:atIndex:)];
        [obj swizzleInstanceMethod:@selector(deleteCharactersInRange:) withMethod:@selector(hookDeleteCharactersInRange:)];
        [obj swizzleInstanceMethod:@selector(stringByAppendingString:) withMethod:@selector(hookStringByAppendingString:)];
        [obj swizzleInstanceMethod:@selector(substringFromIndex:) withMethod:@selector(hookSubstringFromIndex:)];
        [obj swizzleInstanceMethod:@selector(substringToIndex:) withMethod:@selector(hookSubstringToIndex:)];
        [obj swizzleInstanceMethod:@selector(substringWithRange:) withMethod:@selector(hookSubstringWithRange:)];
    });
}

- (nullable instancetype)hookInitWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding
{
    if (NULL != nullTerminatedCString){
        return [self hookInitWithCString:nullTerminatedCString encoding:encoding];
    }
    SafeAssert(NO, @"NSMutableString invalid args hookInitWithCString nil cstring");
    return nil;
}

- (void)hookAppendString:(NSString *)aString
{
    if (aString){
        [self hookAppendString:aString];
        return;
    }
    SafeAssert(NO, @"NSMutableString invalid args hookAppendString:[%@]", aString);
}

- (void)hookInsertString:(NSString *)aString atIndex:(NSUInteger)loc
{
    if (aString && loc <= self.length) {
        [self hookInsertString:aString atIndex:loc];
        return;
    }
    SafeAssert(NO, @"NSMutableString invalid args hookInsertString:[%@] atIndex:[%@]", aString, @(loc));
}

- (void)hookDeleteCharactersInRange:(NSRange)range
{
    if (range.location + range.length <= self.length){
        [self hookDeleteCharactersInRange:range];
        return;
    }
    SafeAssert(NO, @"NSMutableString invalid args hookDeleteCharactersInRange:[%@]", NSStringFromRange(range));
}

- (NSString *)hookStringByAppendingString:(NSString *)aString
{
    if (aString){
        return [self hookStringByAppendingString:aString];
    }
    SafeAssert(NO, @"NSMutableString invalid args hookStringByAppendingString:[%@]", aString);
    return self;
}

- (NSString *)hookSubstringFromIndex:(NSUInteger)from
{
    if (from <= self.length) {
        return [self hookSubstringFromIndex:from];
    }
    SafeAssert(NO, @"NSMutableString invalid args hookSubstringFromIndex:[%ld]", from);
    return nil;
}

- (NSString *)hookSubstringToIndex:(NSUInteger)to
{
    if (to <= self.length) {
        return [self hookSubstringToIndex:to];
    }
    SafeAssert(NO, @"NSMutableString invalid args hookSubstringToIndex:[%ld]", to);
    return self;
}

- (NSString *)hookSubstringWithRange:(NSRange)range
{
    if (range.location + range.length <= self.length) {
        return [self hookSubstringWithRange:range];
    }else if (range.location < self.length){
        return [self hookSubstringWithRange:NSMakeRange(range.location, self.length-range.location)];
    }
    //TODO: - 这里的 NSRange 输出方式存在问题
    SafeAssert(NO, @"NSMutableString invalid args hookSubstringWithRange:[%ld]", range);
    return nil;
}

@end

#pragma mark - NSAttributedString/NSMutableAttributedString 安全机制

@implementation NSAttributedString (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        /* init方法 */
        NSAttributedString* obj = [NSAttributedString alloc];
        [obj swizzleInstanceMethod:@selector(initWithString:) withMethod:@selector(hookInitWithString:)];
        
        /* 普通方法 */
        obj = [[NSAttributedString alloc] init];
        [obj swizzleInstanceMethod:@selector(attributedSubstringFromRange:) withMethod:@selector(hookAttributedSubstringFromRange:)];
        [obj swizzleInstanceMethod:@selector(attribute:atIndex:effectiveRange:) withMethod:@selector(hookAttribute:atIndex:effectiveRange:)];
        [obj swizzleInstanceMethod:@selector(enumerateAttribute:inRange:options:usingBlock:) withMethod:@selector(hookEnumerateAttribute:inRange:options:usingBlock:)];
        [obj swizzleInstanceMethod:@selector(enumerateAttributesInRange:options:usingBlock:) withMethod:@selector(hookEnumerateAttributesInRange:options:usingBlock:)];
    });
}

- (id)hookInitWithString:(NSString*)str {
    if (str){
        return [self hookInitWithString:str];
    }
    SafeAssert(NO, @"hookInitWithString: value is nil");
    return nil;
}

- (id)hookAttribute:(NSAttributedStringKey)attrName atIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range
{
    if (location < self.length){
        return [self hookAttribute:attrName atIndex:location effectiveRange:range];
    }
    SafeAssert(NO, @"hookInitWithString: location is over");
    return nil;
}

- (NSAttributedString *)hookAttributedSubstringFromRange:(NSRange)range {
    if (range.location + range.length <= self.length) {
        return [self hookAttributedSubstringFromRange:range];
    }else if (range.location < self.length){
        return [self hookAttributedSubstringFromRange:NSMakeRange(range.location, self.length-range.location)];
    }
    SafeAssert(NO, @"hookAttributedSubstringFromRange: range is over");
    return nil;
}

- (void)hookEnumerateAttribute:(NSString *)attrName inRange:(NSRange)range options:(NSAttributedStringEnumerationOptions)opts usingBlock:(void (^)(id _Nullable, NSRange, BOOL * _Nonnull))block
{
    if (range.location + range.length <= self.length) {
        [self hookEnumerateAttribute:attrName inRange:range options:opts usingBlock:block];
    }else if (range.location < self.length){
        [self hookEnumerateAttribute:attrName inRange:NSMakeRange(range.location, self.length-range.location) options:opts usingBlock:block];
    }else {
        SafeAssert(NO, @"hookEnumerateAttribute: range is over");
    }
}

- (void)hookEnumerateAttributesInRange:(NSRange)range options:(NSAttributedStringEnumerationOptions)opts usingBlock:(void (^)(NSDictionary<NSString*,id> * _Nonnull, NSRange, BOOL * _Nonnull))block
{
    if (range.location + range.length <= self.length) {
        [self hookEnumerateAttributesInRange:range options:opts usingBlock:block];
    }else if (range.location < self.length){
        [self hookEnumerateAttributesInRange:NSMakeRange(range.location, self.length-range.location) options:opts usingBlock:block];
    }else {
        SafeAssert(NO, @"hookEnumerateAttributesInRange: range is over");
    }
}

@end

@implementation NSMutableAttributedString (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        /* init方法 */
        NSMutableAttributedString* obj = [NSMutableAttributedString alloc];
        [obj swizzleInstanceMethod:@selector(initWithString:) withMethod:@selector(hookInitWithString:)];
        [obj swizzleInstanceMethod:@selector(initWithString:attributes:) withMethod:@selector(hookInitWithString:attributes:)];
        
        /* 普通方法 */
        obj = [[NSMutableAttributedString alloc] init];
        [obj swizzleInstanceMethod:@selector(attributedSubstringFromRange:) withMethod:@selector(hookAttributedSubstringFromRange:)];
        [obj swizzleInstanceMethod:@selector(attribute:atIndex:effectiveRange:) withMethod:@selector(hookAttribute:atIndex:effectiveRange:)];
        [obj swizzleInstanceMethod:@selector(addAttribute:value:range:) withMethod:@selector(hookAddAttribute:value:range:)];
        [obj swizzleInstanceMethod:@selector(addAttributes:range:) withMethod:@selector(hookAddAttributes:range:)];
        [obj swizzleInstanceMethod:@selector(setAttributes:range:) withMethod:@selector(hookSetAttributes:range:)];
        [obj swizzleInstanceMethod:@selector(removeAttribute:range:) withMethod:@selector(hookRemoveAttribute:range:)];
        [obj swizzleInstanceMethod:@selector(deleteCharactersInRange:) withMethod:@selector(hookDeleteCharactersInRange:)];
        [obj swizzleInstanceMethod:@selector(replaceCharactersInRange:withString:) withMethod:@selector(hookReplaceCharactersInRange:withString:)];
        [obj swizzleInstanceMethod:@selector(replaceCharactersInRange:withAttributedString:) withMethod:@selector(hookReplaceCharactersInRange:withAttributedString:)];
        [obj swizzleInstanceMethod:@selector(enumerateAttribute:inRange:options:usingBlock:) withMethod:@selector(hookEnumerateAttribute:inRange:options:usingBlock:)];
        [obj swizzleInstanceMethod:@selector(enumerateAttributesInRange:options:usingBlock:) withMethod:@selector(hookEnumerateAttributesInRange:options:usingBlock:)];
    });
}

- (id)hookInitWithString:(NSString*)str {
    if (str){
        return [self hookInitWithString:str];
    }
    SafeAssert(NO, @"hookInitWithString: value is nil");
    return nil;
}

- (id)hookInitWithString:(NSString*)str attributes:(nullable NSDictionary*)attributes{
    if (str){
        return [self hookInitWithString:str attributes:attributes];
    }
    SafeAssert(NO, @"hookInitWithString: attributes: value is nil");
    return nil;
}

- (NSAttributedString *)hookAttributedSubstringFromRange:(NSRange)range {
    if (range.location + range.length <= self.length) {
        return [self hookAttributedSubstringFromRange:range];
    }else if (range.location < self.length){
        return [self hookAttributedSubstringFromRange:NSMakeRange(range.location, self.length-range.location)];
    }
    SafeAssert(NO, @"hookAttributedSubstringFromRange: range is over");
    return nil;
}

- (id)hookAttribute:(NSAttributedStringKey)attrName atIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range
{
    if (location < self.length){
        return [self hookAttribute:attrName atIndex:location effectiveRange:range];
    }
    SafeAssert(NO, @"hookAttribute:atIndex:effectiveRange: location is over");
    return nil;
}

- (void)hookAddAttribute:(id)name value:(id)value range:(NSRange)range {
    if (!range.length) {
        [self hookAddAttribute:name value:value range:range];
    }else if (value){
        if (range.location + range.length <= self.length) {
            [self hookAddAttribute:name value:value range:range];
        }else if (range.location < self.length){
            [self hookAddAttribute:name value:value range:NSMakeRange(range.location, self.length-range.location)];
        }
    }else {
        SafeAssert(NO, @"hookAddAttribute:value:range: value is nil");
    }
}

- (void)hookAddAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {
    if (!range.length) {
        [self hookAddAttributes:attrs range:range];
    }else if (attrs){
        if (range.location + range.length <= self.length) {
            [self hookAddAttributes:attrs range:range];
        }else if (range.location < self.length){
            [self hookAddAttributes:attrs range:NSMakeRange(range.location, self.length-range.location)];
        }
    }else{
        SafeAssert(NO, @"hookAddAttributes:range: attrs is nil");
    }
}

- (void)hookSetAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {
    if (!range.length) {
        [self hookSetAttributes:attrs range:range];
    }else if (attrs){
        if (range.location + range.length <= self.length) {
            [self hookSetAttributes:attrs range:range];
        }else if (range.location < self.length){
            [self hookSetAttributes:attrs range:NSMakeRange(range.location, self.length-range.location)];
        }
    }else{
        SafeAssert(NO, @"hookSetAttributes:range: attrs is nil");
    }
}

- (void)hookRemoveAttribute:(id)name range:(NSRange)range {
    if (!range.length) {
        [self hookRemoveAttribute:name range:range];
    }else if (name){
        if (range.location + range.length <= self.length) {
            [self hookRemoveAttribute:name range:range];
        }else if (range.location < self.length) {
            [self hookRemoveAttribute:name range:NSMakeRange(range.location, self.length-range.location)];
        }
    }else{
        SafeAssert(NO, @"hookRemoveAttribute:range: name is nil");
    }
}

- (void)hookDeleteCharactersInRange:(NSRange)range {
    if (range.location + range.length <= self.length) {
        [self hookDeleteCharactersInRange:range];
    }else if (range.location < self.length) {
        [self hookDeleteCharactersInRange:NSMakeRange(range.location, self.length-range.location)];
    }else {
        SafeAssert(NO, @"hookDeleteCharactersInRange: range is over");
    }
}

- (void)hookReplaceCharactersInRange:(NSRange)range withString:(NSString *)str {
    if (str){
        if (range.location + range.length <= self.length) {
            [self hookReplaceCharactersInRange:range withString:str];
        }else if (range.location < self.length) {
            [self hookReplaceCharactersInRange:NSMakeRange(range.location, self.length-range.location) withString:str];
        }
    }else{
        SafeAssert(NO, @"hookReplaceCharactersInRange:withString: str is nil");
    }
}

- (void)hookReplaceCharactersInRange:(NSRange)range withAttributedString:(NSString *)str {
    if (str){
        if (range.location + range.length <= self.length) {
            [self hookReplaceCharactersInRange:range withAttributedString:str];
        }else if (range.location < self.length) {
            [self hookReplaceCharactersInRange:NSMakeRange(range.location, self.length-range.location) withAttributedString:str];
        }
    }else{
        SafeAssert(NO, @"hookReplaceCharactersInRange:withString: str is nil");
    }
}

- (void)hookEnumerateAttribute:(NSString*)attrName inRange:(NSRange)range options:(NSAttributedStringEnumerationOptions)opts usingBlock:(void (^)(id _Nullable, NSRange, BOOL * _Nonnull))block
{
    if (range.location + range.length <= self.length) {
        [self hookEnumerateAttribute:attrName inRange:range options:opts usingBlock:block];
    }else if (range.location < self.length){
        [self hookEnumerateAttribute:attrName inRange:NSMakeRange(range.location, self.length-range.location) options:opts usingBlock:block];
    }else{
        SafeAssert(NO, @"hookEnumerateAttribute:inRange:options:usingBlock: range is over");
    }
}

- (void)hookEnumerateAttributesInRange:(NSRange)range options:(NSAttributedStringEnumerationOptions)opts usingBlock:(void (^)(NSDictionary<NSString*,id> * _Nonnull, NSRange, BOOL * _Nonnull))block
{
    if (range.location + range.length <= self.length) {
        [self hookEnumerateAttributesInRange:range options:opts usingBlock:block];
    }else if (range.location < self.length){
        [self hookEnumerateAttributesInRange:NSMakeRange(range.location, self.length-range.location) options:opts usingBlock:block];
    }else{
        SafeAssert(NO, @"hookEnumerateAttributesInRange:options:usingBlock: range is over");
    }
}

@end

#pragma mark - NSArray/NSMutableArray 安全机制

@implementation NSArray (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法不用在NSMutableArray里再swizz一次 */
        [NSArray swizzleClassMethod:@selector(arrayWithObject:) withMethod:@selector(hookArrayWithObject:)];
        [NSArray swizzleClassMethod:@selector(arrayWithObjects:count:) withMethod:@selector(hookArrayWithObjects:count:)];
        
        /* 数组有内容obj类型才是__NSArrayI */
        NSArray* obj = [[NSArray alloc] initWithObjects:@0, @1, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
        [obj swizzleInstanceMethod:@selector(objectAtIndexedSubscript:) withMethod:@selector(hookObjectAtIndexedSubscript:)];
        
        /* iOS10 以上，单个内容类型是__NSArraySingleObjectI */
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0){
            obj = [[NSArray alloc] initWithObjects:@0, nil];
            [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
            [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
            [obj swizzleInstanceMethod:@selector(objectAtIndexedSubscript:) withMethod:@selector(hookObjectAtIndexedSubscript:)];
        }
        
        /* iOS9 以上，没内容类型是__NSArray0 */
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0){
            obj = [[NSArray alloc] init];
            [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex0:)];
            [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
            [obj swizzleInstanceMethod:@selector(objectAtIndexedSubscript:) withMethod:@selector(hookObjectAtIndexedSubscript:)];
        }
        
    });
}

+ (instancetype)hookArrayWithObject:(id)anObject
{
    if (anObject) {
        return [self hookArrayWithObject:anObject];
    }
    SafeAssert(NO, @"NSArray invalid args hookArrayWithObject:[%@]", anObject);
    return nil;
}

/* __NSArray0 没有元素，也不可以变 */
- (id)hookObjectAtIndex0:(NSUInteger)index {
    SafeAssert(NO, @"NSArray invalid index:[%@]", @(index));
    return nil;
}

- (id)hookObjectAtIndex:(NSUInteger)index {
    if (index < self.count) {
        return [self hookObjectAtIndex:index];
    }
    SafeAssert(NO, @"NSArray invalid index:[%@]", @(index));
    return nil;
}

- (id)hookObjectAtIndexedSubscript:(NSInteger)index {
    if (index < self.count) {
        return [self hookObjectAtIndexedSubscript:index];
    }
    SafeAssert(NO, @"NSArray invalid index:[%@]", @(index));
    return nil;
}

- (NSArray *)hookSubarrayWithRange:(NSRange)range
{
    if (range.location + range.length <= self.count){
        return [self hookSubarrayWithRange:range];
    }else if (range.location < self.count){
        return [self hookSubarrayWithRange:NSMakeRange(range.location, self.count-range.location)];
    }
    SafeAssert(NO, @"hookSubarrayWithRange: range is over");
    return nil;
}

+ (instancetype)hookArrayWithObjects:(const id [])objects count:(NSUInteger)count
{
    NSInteger index = 0;
    id objs[count];
    for (NSInteger i = 0; i < count ; ++i) {
        if (objects[i]) {
            objs[index++] = objects[i];
        }else {
            SafeAssert(NO, @"hookArrayWithObjects: count is over");
        }
    }
    return [self hookArrayWithObjects:objs count:index];
}

@end

@implementation NSMutableArray (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableArray* obj = [[NSMutableArray alloc] init];
        //对象方法 __NSArrayM 和 __NSArrayI 都有实现，都要swizz
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(objectAtIndexedSubscript:) withMethod:@selector(hookObjectAtIndexedSubscript:)];
        
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(insertObject:atIndex:) withMethod:@selector(hookInsertObject:atIndex:)];
        [obj swizzleInstanceMethod:@selector(removeObjectAtIndex:) withMethod:@selector(hookRemoveObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(hookReplaceObjectAtIndex:withObject:)];
        [obj swizzleInstanceMethod:@selector(removeObjectsInRange:) withMethod:@selector(hookRemoveObjectsInRange:)];
        [obj swizzleInstanceMethod:@selector(subarrayWithRange:) withMethod:@selector(hookSubarrayWithRange:)];
        
    });
}

- (void)hookAddObject:(id)anObject {
    if (anObject) {
        [self hookAddObject:anObject];
    } else {
        SafeAssert(NO, @"NSMutableArray invalid args hookAddObject:[%@]", anObject);
    }
}

- (id)hookObjectAtIndex:(NSUInteger)index {
    if (index < self.count) {
        return [self hookObjectAtIndex:index];
    }
    SafeAssert(NO, @"NSArray invalid index:[%@]", @(index));
    return nil;
}

- (id)hookObjectAtIndexedSubscript:(NSInteger)index {
    if (index < self.count) {
        return [self hookObjectAtIndexedSubscript:index];
    }
    SafeAssert(NO, @"NSArray invalid index:[%@]", @(index));
    return nil;
}

- (void)hookInsertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject && index <= self.count) {
        [self hookInsertObject:anObject atIndex:index];
    } else {
        if (!anObject) {
            SafeAssert(NO, @"NSMutableArray invalid args hookInsertObject:[%@] atIndex:[%@]", anObject, @(index));
        }
        if (index > self.count) {
            SafeAssert(NO, @"NSMutableArray hookInsertObject[%@] atIndex:[%@] out of bound:[%@]", anObject, @(index), @(self.count));
        }
    }
}

- (void)hookRemoveObjectAtIndex:(NSUInteger)index {
    if (index < self.count) {
        [self hookRemoveObjectAtIndex:index];
    } else {
        SafeAssert(NO, @"NSMutableArray hookRemoveObjectAtIndex:[%@] out of bound:[%@]", @(index), @(self.count));
    }
}

- (void)hookReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
    if (index < self.count && anObject) {
        [self hookReplaceObjectAtIndex:index withObject:anObject];
    } else {
        if (!anObject) {
            SafeAssert(NO, @"NSMutableArray invalid args hookReplaceObjectAtIndex:[%@] withObject:[%@]", @(index), anObject);
        }
        if (index >= self.count) {
            SafeAssert(NO, @"NSMutableArray hookReplaceObjectAtIndex:[%@] withObject:[%@] out of bound:[%@]", @(index), anObject, @(self.count));
        }
    }
}

- (void)hookRemoveObjectsInRange:(NSRange)range {
    if (range.location + range.length <= self.count) {
        [self hookRemoveObjectsInRange:range];
    }else {
        SafeAssert(NO, @"NSMutableArray invalid args hookRemoveObjectsInRange:[%@]", NSStringFromRange(range));
    }
}

- (NSArray *)hookSubarrayWithRange:(NSRange)range
{
    if (range.location + range.length <= self.count){
        return [self hookSubarrayWithRange:range];
    }else if (range.location < self.count){
        return [self hookSubarrayWithRange:NSMakeRange(range.location, self.count-range.location)];
    }
    SafeAssert(NO, @"hookSubarrayWithRange: range is over");
    return nil;
}

@end

#pragma mark - NSDictionary/NSMutableDictionary 安全机制

@implementation NSDictionary (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法 */
        [NSDictionary swizzleClassMethod:@selector(dictionaryWithObject:forKey:) withMethod:@selector(hookDictionaryWithObject:forKey:)];
        [NSDictionary swizzleClassMethod:@selector(dictionaryWithObjects:forKeys:count:) withMethod:@selector(hookDictionaryWithObjects:forKeys:count:)];
        
        /* 数组有内容obj类型才是__NSDictionaryI */
        NSDictionary* obj = [[NSDictionary alloc] initWithObjectsAndKeys:@0, @0, @0, @0, nil];
        [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        
        /* iOS10 以上，单个内容类型是__NSArraySingleEntryDictionaryI */
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0){
            obj = [[NSDictionary alloc] initWithObjectsAndKeys:@0, @0, nil];
            [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        }
        
        /* iOS9 以上，没内容类型是__NSDictionary0 */
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0){
            obj = [[NSDictionary alloc] init];
            [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        }
    });
}

+ (instancetype)hookDictionaryWithObject:(id)object forKey:(id)key
{
    if (object && key) {
        return [self hookDictionaryWithObject:object forKey:key];
    }
    SafeAssert(NO, @"NSDictionary invalid args hookDictionaryWithObject:[%@] forKey:[%@]", object, key);
    return nil;
}

+ (instancetype)hookDictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt
{
    NSInteger index = 0;
    id ks[cnt];
    id objs[cnt];
    for (NSInteger i = 0; i < cnt ; ++i) {
        if (keys[i] && objects[i]) {
            ks[index] = keys[i];
            objs[index] = objects[i];
            ++index;
        } else {
            SafeAssert(NO, @"NSDictionary invalid args hookDictionaryWithObject:[%@] forKey:[%@]", objects[i], keys[i]);
        }
    }
    return [self hookDictionaryWithObjects:objs forKeys:ks count:index];
}

- (id)hookObjectForKey:(id)aKey
{
    if (aKey){
        return [self hookObjectForKey:aKey];
    }
    SafeAssert(NO, @"hookObjectForKey: value is nil");
    return nil;
}

@end

@implementation NSMutableDictionary (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableDictionary* obj = [[NSMutableDictionary alloc] init];
        [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
        [obj swizzleInstanceMethod:@selector(removeObjectForKey:) withMethod:@selector(hookRemoveObjectForKey:)];
    });
}

- (id)hookObjectForKey:(id)aKey
{
    if (aKey){
        return [self hookObjectForKey:aKey];
    }
    SafeAssert(NO, @"hookObjectForKey: value is nil");
    return nil;
}

- (void)hookSetObject:(id)anObject forKey:(id)aKey {
    if (anObject && aKey) {
        [self hookSetObject:anObject forKey:aKey];
    } else {
        SafeAssert(NO, @"NSMutableDictionary invalid args hookSetObject:[%@] forKey:[%@]", anObject, aKey);
    }
}

- (void)hookRemoveObjectForKey:(id)aKey {
    if (aKey) {
        [self hookRemoveObjectForKey:aKey];
    } else {
        SafeAssert(NO, @"NSMutableDictionary invalid args hookRemoveObjectForKey:[%@]", aKey);
    }
}

@end

#pragma mark - NSSet/NSMutableSet 安全机制

@implementation NSSet (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法 */
        [NSSet swizzleClassMethod:@selector(setWithObject:) withMethod:@selector(hookSetWithObject:)];
    });
}

+ (instancetype)hookSetWithObject:(id)object
{
    if (object){
        return [self hookSetWithObject:object];
    }
    SafeAssert(NO, @"NSSet invalid args hookSetWithObject:[%@]", object);
    return nil;
}

@end

@implementation NSMutableSet (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 普通方法 */
        NSMutableSet* obj = [NSMutableSet setWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(removeObject:) withMethod:@selector(hookRemoveObject:)];
    });
}

- (void)hookAddObject:(id)object {
    if (object) {
        [self hookAddObject:object];
    } else {
        SafeAssert(NO, @"NSMutableSet invalid args hookAddObject[%@]", object);
    }
}

- (void)hookRemoveObject:(id)object {
    if (object) {
        [self hookRemoveObject:object];
    } else {
        SafeAssert(NO, @"NSMutableSet invalid args hookRemoveObject[%@]", object);
    }
}

@end

#pragma mark - NSOrderedSet/NSMutableOrderedSet 安全机制

@implementation NSOrderedSet (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 类方法 */
        [NSOrderedSet swizzleClassMethod:@selector(orderedSetWithObject:) withMethod:@selector(hookOrderedSetWithObject:)];
        
        /* init方法:[NSOrderedSet alloc] 和 [NSMutableOrderedSet alloc] 返回的类是一样   */
        NSOrderedSet* obj = [NSOrderedSet alloc];
        [obj swizzleInstanceMethod:@selector(initWithObject:) withMethod:@selector(hookInitWithObject:)];
        
        /* 普通方法 */
        obj = [NSOrderedSet orderedSetWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
    });
}

+ (instancetype)hookOrderedSetWithObject:(id)object
{
    if (object) {
        return [self hookOrderedSetWithObject:object];
    }
    SafeAssert(NO, @"NSOrderedSet invalid args hookOrderedSetWithObject:[%@]", object);
    return nil;
}

- (instancetype)hookInitWithObject:(id)object
{
    if (object){
        return [self hookInitWithObject:object];
    }
    SafeAssert(NO, @"NSOrderedSet invalid args hookInitWithObject:[%@]", object);
    return nil;
}

- (id)hookObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        return [self hookObjectAtIndex:idx];
    }
    SafeAssert(NO, @"hookObjectAtIndex: idx is over");
    return nil;
}

@end

@implementation NSMutableOrderedSet (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /* 普通方法 */
        NSMutableOrderedSet* obj = [NSMutableOrderedSet orderedSetWithObjects:@0, nil];
        [obj swizzleInstanceMethod:@selector(objectAtIndex:) withMethod:@selector(hookObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(addObject:) withMethod:@selector(hookAddObject:)];
        [obj swizzleInstanceMethod:@selector(removeObjectAtIndex:) withMethod:@selector(hookRemoveObjectAtIndex:)];
        [obj swizzleInstanceMethod:@selector(insertObject:atIndex:) withMethod:@selector(hookInsertObject:atIndex:)];
        [obj swizzleInstanceMethod:@selector(replaceObjectAtIndex:withObject:) withMethod:@selector(hookReplaceObjectAtIndex:withObject:)];
    });
}

- (id)hookObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        return [self hookObjectAtIndex:idx];
    }
    SafeAssert(NO, @"hookObjectAtIndex: idx is over");
    return nil;
}

- (void)hookAddObject:(id)object {
    if (object) {
        [self hookAddObject:object];
    } else {
        SafeAssert(NO, @"NSMutableOrderedSet invalid args hookAddObject:[%@]", object);
    }
}

- (void)hookInsertObject:(id)object atIndex:(NSUInteger)idx
{
    if (object && idx <= self.count) {
        [self hookInsertObject:object atIndex:idx];
    }else{
        SafeAssert(NO, @"NSMutableOrderedSet invalid args hookInsertObject:[%@] atIndex:[%@]", object, @(idx));
    }
}

- (void)hookRemoveObjectAtIndex:(NSUInteger)idx
{
    if (idx < self.count){
        [self hookRemoveObjectAtIndex:idx];
    }else{
        SafeAssert(NO, @"NSMutableOrderedSet invalid args hookRemoveObjectAtIndex:[%@]", @(idx));
    }
}

- (void)hookReplaceObjectAtIndex:(NSUInteger)idx withObject:(id)object
{
    if (object && idx < self.count) {
        [self hookReplaceObjectAtIndex:idx withObject:object];
    }else{
        SafeAssert(NO, @"NSMutableOrderedSet invalid args hookReplaceObjectAtIndex:[%@] withObject:[%@]", @(idx), object);
    }
}

@end

#pragma mark - NSUserDefaults 安全机制

@implementation NSUserDefaults (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSUserDefaults* obj = [[NSUserDefaults alloc] init];
        [obj swizzleInstanceMethod:@selector(objectForKey:) withMethod:@selector(hookObjectForKey:)];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
        [obj swizzleInstanceMethod:@selector(removeObjectForKey:) withMethod:@selector(hookRemoveObjectForKey:)];
        
        [obj swizzleInstanceMethod:@selector(integerForKey:) withMethod:@selector(hookIntegerForKey:)];
        [obj swizzleInstanceMethod:@selector(boolForKey:) withMethod:@selector(hookBoolForKey:)];
    });
}

- (id)hookObjectForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookObjectForKey:defaultName];
    }
    SafeAssert(NO, @"hookObjectForKey: defaultName is nil");
    return nil;
}

- (NSInteger)hookIntegerForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookIntegerForKey:defaultName];
    }
    SafeAssert(NO, @"hookIntegerForKey: defaultName is nil");
    return 0;
}

- (BOOL)hookBoolForKey:(NSString *)defaultName
{
    if (defaultName) {
        return [self hookBoolForKey:defaultName];
    }
    SafeAssert(NO, @"hookBoolForKey: defaultName is nil");
    return NO;
}

- (void)hookSetObject:(id)value forKey:(NSString*)aKey
{
    if (aKey) {
        [self hookSetObject:value forKey:aKey];
    } else {
        SafeAssert(NO, @"NSUserDefaults invalid args hookSetObject:[%@] forKey:[%@]", value, aKey);
    }
}

- (void)hookRemoveObjectForKey:(NSString*)aKey
{
    if (aKey) {
        [self hookRemoveObjectForKey:aKey];
    } else {
        SafeAssert(NO, @"NSUserDefaults invalid args hookRemoveObjectForKey:[%@]", aKey);
    }
}

@end

#pragma mark - NSCache 安全机制

@implementation NSCache (ACSafe)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSCache* obj = [[NSCache alloc] init];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:) withMethod:@selector(hookSetObject:forKey:)];
        [obj swizzleInstanceMethod:@selector(setObject:forKey:cost:) withMethod:@selector(hookSetObject:forKey:cost:)];
    });
}

- (void)hookSetObject:(id)obj forKey:(id)key // 0 cost
{
    if (obj && key) {
        [self hookSetObject:obj forKey:key];
    }else {
        SafeAssert(NO, @"NSCache invalid args hookSetObject:[%@] forKey:[%@]", obj, key);
    }
}

- (void)hookSetObject:(id)obj forKey:(id)key cost:(NSUInteger)g
{
    if (obj && key) {
        [self hookSetObject:obj forKey:key cost:g];
    }else {
        SafeAssert(NO, @"NSCache invalid args hookSetObject:[%@] forKey:[%@] cost:[%@]", obj, key, @(g));
    }
}
@end
